use bytemuck::Pod;
use lyon::{
    lyon_tessellation::{
        BuffersBuilder, FillOptions, FillTessellator, FillVertex, FillVertexConstructor,
        StrokeOptions, StrokeTessellator, StrokeVertexConstructor, VertexBuffers,
    },
    math::{Box2D, Point, Size},
    path::{builder::BorderRadii, Path},
};
use piet_cosmic_text::cosmic_text::Color;
use piet_cosmic_text::TextLayout;
use wgpu::{
    util::{BufferInitDescriptor, DeviceExt},
    BufferUsages, CommandEncoder, RenderPassDescriptor, StoreOp, SurfaceTexture, TextureView,
};

use crate::primitive_pipeline::Vertex;
use crate::{Graphics, Texture, Transform3D, Vector};

pub struct Frame<'a> {
    pub g: &'a mut Graphics,
    pub encoder: CommandEncoder,
    pub view: TextureView,
    pub output: SurfaceTexture,
    pub translate: Point,
    pub clip_rect: Option<Box2D>,
}

impl<'a> Frame<'a> {
    /// закрашивает экран заданным цветом
    pub fn clear(&mut self, color: [f64; 4]) {
        {
            let color_attachment = wgpu::RenderPassColorAttachment {
                view: &self.g.msaa_framebuffer,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Clear(wgpu::Color {
                        r: color[0],
                        g: color[1],
                        b: color[2],
                        a: color[3],
                    }),
                    store: StoreOp::Store,
                },
                resolve_target: Some(&self.view),
            };
            let _ = self.encoder.begin_render_pass(&RenderPassDescriptor {
                label: Some("Render Pass"),
                color_attachments: &[Some(color_attachment)],
                ..Default::default()
            });
        }
    }

    pub fn set_clip(&mut self, rect: Option<Box2D>) {
        self.clip_rect = rect;
    }

    /// current translate + origin
    pub fn translate(&mut self, origin: Vector) {
        self.translate += origin
    }

    /// базовый метод рисованию. На вход принимает вершины, индексы и матрицу трансформации
    pub fn draw<T: Pod>(&mut self, vertexes: &[T], indexes: &[u16], transform: Transform3D<f32>) {
        let color_attachment = wgpu::RenderPassColorAttachment {
            view: &self.g.msaa_framebuffer,
            ops: wgpu::Operations {
                load: wgpu::LoadOp::Load,
                store: StoreOp::Store,
            },
            resolve_target: Some(&self.view),
        };
        let transform = transform.pre_translate(self.translate.to_vector().to_3d());
        let transform_binding = self.g.transform_binding(transform);
        let vbo = self.g.device.create_buffer_init(&BufferInitDescriptor {
            label: None,
            contents: bytemuck::cast_slice(vertexes),
            usage: BufferUsages::VERTEX,
        });
        let ibo = self.g.device.create_buffer_init(&BufferInitDescriptor {
            label: None,
            contents: bytemuck::cast_slice(indexes),
            usage: BufferUsages::INDEX,
        });
        let mut render_pass = self.encoder.begin_render_pass(&RenderPassDescriptor {
            label: Some("Texture render pass"),
            color_attachments: &[Some(color_attachment)],
            ..Default::default()
        });
        render_pass.set_viewport(
            0.0,
            0.0,
            self.g.config.width as f32,
            self.g.config.height as f32,
            0.0,
            1.0,
        );
        if let Some(scissor) = self.clip_rect {
            render_pass.set_scissor_rect(
                scissor.min.x as u32,
                scissor.min.y as u32,
                scissor.width() as u32,
                scissor.height() as u32,
            );
        }
        render_pass.set_pipeline(&self.g.primitive_pipeline);
        render_pass.set_bind_group(0, &self.g.view_binding, &[]);
        render_pass.set_bind_group(1, &transform_binding, &[]);
        render_pass.set_vertex_buffer(0, vbo.slice(..));
        render_pass.set_index_buffer(ibo.slice(..), wgpu::IndexFormat::Uint16);
        render_pass.draw_indexed(0..indexes.len() as u32, 0, 0..1);
    }

    /// Отрисовывает заданную текстуру
    ///
    /// # Arguments
    ///
    /// * `texture`: текстура
    /// * `transform`: матрица трансформации
    ///
    /// returns: ()
    ///
    /// # Examples
    ///
    /// ```
    /// use graphics::Transform;
    /// let img = image::load_from_memory(include_bytes!("image.png")).unwrap();
    /// let texture = graphics.load_texture(&img, Some("Image"));
    /// // отрисуем тексутуру 400x400 пикселей
    /// frame.draw_texture(&texture, Transform::scale(400.0, 400.0))
    /// ```
    pub fn draw_texture(&mut self, texture: &Texture, transform: Transform3D<f32>) {
        let texture_binding = self.g.texture_binding(&texture);
        let transform = transform.pre_translate(self.translate.to_vector().to_3d());
        let transform_binding = self.g.transform_binding(transform);
        let color_attachment = wgpu::RenderPassColorAttachment {
            view: &self.g.msaa_framebuffer,
            ops: wgpu::Operations {
                load: wgpu::LoadOp::Load,
                store: StoreOp::Store,
            },
            resolve_target: Some(&self.view),
        };
        let mut render_pass = self.encoder.begin_render_pass(&RenderPassDescriptor {
            label: Some("Texture render pass"),
            color_attachments: &[Some(color_attachment)],
            ..Default::default()
        });
        render_pass.set_viewport(
            0.0,
            0.0,
            self.g.config.width as f32,
            self.g.config.height as f32,
            0.0,
            1.0,
        );
        if let Some(scissor) = self.clip_rect {
            render_pass.set_scissor_rect(
                scissor.min.x as u32,
                scissor.min.y as u32,
                scissor.width() as u32,
                scissor.height() as u32,
            );
        }
        render_pass.set_pipeline(&self.g.texture_pipeline);
        render_pass.set_bind_group(0, &self.g.view_binding, &[]);
        render_pass.set_bind_group(1, &transform_binding, &[]);
        render_pass.set_bind_group(2, &texture_binding, &[]);
        render_pass.set_vertex_buffer(0, self.g.texture_vbo.slice(..));
        render_pass.set_index_buffer(self.g.texture_ibo.slice(..), wgpu::IndexFormat::Uint16);
        render_pass.draw_indexed(0..self.g.texture_indices, 0, 0..1);
    }

    /// используется в самом конце чтобы отобразить изменения
    pub fn submit(self) {
        self.g.queue.submit(Some(self.encoder.finish()));
        self.output.present();
    }
}

struct WithColor([f64; 4]);

impl FillVertexConstructor<Vertex> for WithColor {
    fn new_vertex(&mut self, vertex: FillVertex) -> Vertex {
        Vertex {
            position: vertex.position().to_3d().to_array(),
            color: [
                self.0[0] as f32,
                self.0[1] as f32,
                self.0[2] as f32,
                self.0[3] as f32,
            ],
        }
    }
}

impl StrokeVertexConstructor<Vertex> for WithColor {
    fn new_vertex(&mut self, vertex: lyon::lyon_tessellation::StrokeVertex) -> Vertex {
        Vertex {
            position: vertex.position().to_3d().to_array(),
            color: [
                self.0[0] as f32,
                self.0[1] as f32,
                self.0[2] as f32,
                self.0[3] as f32,
            ],
        }
    }
}

lazy_static::lazy_static! {
    pub static ref FILL_OPTIONS: FillOptions = FillOptions::tolerance(0.001);
    pub static ref STROKE_OPTIONS: StrokeOptions = StrokeOptions::tolerance(0.001);

}
// basic shapes for simple drawing
impl<'a> Frame<'a> {
    pub fn draw_path_fill(&mut self, path: &Path, color: [f64; 4], options: &FillOptions) {
        let mut geometry: VertexBuffers<Vertex, u16> = VertexBuffers::new();
        let mut tessellator = FillTessellator::new();

        {
            tessellator
                .tessellate_path(
                    path,
                    options,
                    &mut BuffersBuilder::new(&mut geometry, WithColor(color)),
                )
                .unwrap()
        }
        self.draw(
            &geometry.vertices,
            &geometry.indices,
            Transform3D::identity(),
        );
    }

    pub fn draw_path_stroke(&mut self, path: &Path, color: [f64; 4], options: &StrokeOptions) {
        let mut geometry: VertexBuffers<Vertex, u16> = VertexBuffers::new();
        let mut tessellator = StrokeTessellator::new();

        {
            tessellator
                .tessellate_path(
                    path,
                    options,
                    &mut BuffersBuilder::new(&mut geometry, WithColor(color)),
                )
                .unwrap()
        }
        self.draw(
            &geometry.vertices,
            &geometry.indices,
            Transform3D::identity(),
        );
    }

    pub fn rect(&mut self, rect: &Box2D, color: [f64; 4]) {
        let rect = [
            Vertex {
                position: [rect.min.x, rect.min.y, 0.0],
                color: [
                    color[0] as f32,
                    color[1] as f32,
                    color[2] as f32,
                    color[3] as f32,
                ],
            },
            Vertex {
                position: [rect.min.x, rect.max.y, 0.0],
                color: [
                    color[0] as f32,
                    color[1] as f32,
                    color[2] as f32,
                    color[3] as f32,
                ],
            },
            Vertex {
                position: [rect.max.x, rect.max.y, 0.0],
                color: [
                    color[0] as f32,
                    color[1] as f32,
                    color[2] as f32,
                    color[3] as f32,
                ],
            },
            Vertex {
                position: [rect.max.x, rect.min.y, 0.0],
                color: [
                    color[0] as f32,
                    color[1] as f32,
                    color[2] as f32,
                    color[3] as f32,
                ],
            },
        ];
        let indices = [0, 1, 2, 0, 2, 3];
        self.draw(&rect, &indices, Transform3D::identity());
    }

    pub fn rect_stroke(&mut self, rect: &Box2D, color: [f64; 4], width: f32) {
        let mut builder = Path::builder();
        builder.add_rectangle(rect, lyon::path::Winding::Positive);
        let path = builder.build();
        self.draw_path_stroke(&path, color, &STROKE_OPTIONS.with_line_width(width));
    }

    pub fn circle(&mut self, center: Point, radius: f32, color: [f64; 4]) {
        let mut builder = Path::builder();
        builder.add_circle(center, radius, lyon::path::Winding::Positive);
        let path = builder.build();
        self.draw_path_fill(&path, color, &FILL_OPTIONS);
    }

    pub fn rounded_rect(&mut self, rect: &Box2D, rounds: &BorderRadii, color: [f64; 4]) {
        let mut builder = Path::builder();
        builder.add_rounded_rectangle(rect, rounds, lyon::path::Winding::Positive);
        let path = builder.build();
        self.draw_path_fill(&path, color, &FILL_OPTIONS);
    }

    pub fn rounded_rect_stroke(
        &mut self,
        rect: &Box2D,
        rounds: &BorderRadii,
        color: [f64; 4],
        width: f32,
    ) {
        let mut builder = Path::builder();
        builder.add_rounded_rectangle(rect, rounds, lyon::path::Winding::Positive);
        let path = builder.build();
        self.draw_path_stroke(&path, color, &STROKE_OPTIONS.with_line_width(width));
    }

    pub fn texture(&mut self, texture: &Texture, pos: &Point, size: &Size) {
        let transform = Transform3D::scale(size.width, size.height, 1.0)
            .then_translate(pos.to_3d().to_vector());
        self.draw_texture(&texture, transform);
    }

    /// Метод для отрисовки текста
    ///
    /// # Arguments
    ///
    /// * `text`: Section у которого игнорируется заданное смещение. Его надо указать отдельно в translate
    /// * `translate`: смещение относительно родительского элемента
    ///
    /// returns: ()
    ///
    /// # Examples
    ///
    /// ```
    /// use lyon::math::Point;
    ///
    /// frame.draw_text("Hello, World!", Point::zero())
    /// ```
    pub fn draw_text(&mut self, text: &TextLayout, color: Color, transform: Transform3D<f32>) {
        // Borrow buffer together with the font system for more convenient method calls
        let mut vertices = Vec::new();
        let mut indices = Vec::new();
        let font_system = self.g.font_system.clone();
        font_system.with_font_system_mut(|font_system| {
            let mut cursor = 0;
            // Draw the buffer (for performance, instead use SwashCache directly)
            text.buffer().draw(
                font_system,
                &mut self.g.swash_cache,
                color,
                |x, y, w, h, color| {
                    vertices.push(Vertex {
                        position: [x as f32, y as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    vertices.push(Vertex {
                        position: [x as f32, (y + h as i32) as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    vertices.push(Vertex {
                        position: [(x + w as i32) as f32, (y + h as i32) as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    vertices.push(Vertex {
                        position: [(x + w as i32) as f32, y as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    indices.push(cursor);
                    indices.push(cursor + 1);
                    indices.push(cursor + 2);
                    indices.push(cursor);
                    indices.push(cursor + 2);
                    indices.push(cursor + 3);
                    cursor += 4
                },
            );
        });
        // Perform shaping as desired
        // buffer.shape_until_scroll();
        self.draw(&vertices, &indices, transform);
    }
}
